package edu.kit.los;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.xml.stream.XMLStreamException;

import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;

import edu.kit.los.event.StandardEvent;

/**
 * @author Felix Kiechle, Benedikt Fuss  AIFB@BKIT
 * Zentrale Klasse zur Modelierung eines Events in RDF. Enthält das Jena-RDF Model, in dem die 
 * Events abgebildet werden.
 *
 */
public class Model {
	
	/**
	 * RDF Model
	 */
	private com.hp.hpl.jena.rdf.model.Model model;


	private static final String xsd = "http://www.w3.org/2001/XMLSchema#";
	private static final String lode = "http://linkedevents.org/ontology/";
	private static final String dc = "http://purl.org/dc/elements/1.1/";
	private static final String wgs84 = "http://www.w3.org/2003/01/geo/wgs84_pos#";
	private static final String time = "http://www.w3.org/2006/time#";
	private Property description;
	private Property publisher;
	private Property geo;
	private Property latitude;
	private Property longitude;
	private Property zeit;
	private Property zeit_start;
	private Property zeit_ende;
	private Property title;
	
	
	/**
	 * Konfiguriert das Jena-RDF Model
	 */
	public Model(){
				
		this.model = ModelFactory.createDefaultModel();
		this.description = model.createProperty(dc + "description");
		this.title = model.createProperty(dc + "title");
		this.publisher = model.createProperty(dc + "publisher");
		this.geo = model.createProperty(lode + "inSpace");
		this.latitude = model.createProperty(wgs84 + "lat");
		this.longitude = model.createProperty(wgs84 + "long");
		this.zeit = model.createProperty(lode + "atTime");
		this.zeit_start = model.createProperty(time + "hasBeginning");
		this.zeit_ende = model.createProperty(time + "hasEnd");

		model.setNsPrefix("xsd", xsd);
		model.setNsPrefix("lode", lode);
		model.setNsPrefix("dc", dc);
		model.setNsPrefix("wgs84", wgs84);
		model.setNsPrefix("time", time);	
	}
	
	/**
	 * Fügt dem RDF-Model ein Event hinzu
	 * Für nicht belegte Eigenschaften (Null-Werte bzw. 0 bei Double)
	 * werden keine Tripel eingefügt. 
	 * @param StandardEvent Event welches dem Model hinzugefügt werden soll
	 */
	public void addEvent(StandardEvent event){
		Resource res = model.createResource(event.getEventUrl());
		if(event.getTitle()!=null) res.addProperty(this.title, event.getTitle());
		if(event.getDescription()!=null) res.addProperty(this.description, event.getDescription());
		if(event.getPublisher()!=null) res.addProperty(this.publisher, event.getPublisher());
		if(event.getGeoLat()!=0 & event.getGeoLong()!=0)
			res.addProperty(this.geo,model.createResource()
				.addProperty(this.latitude, event.getGeoLat()+"")
				.addProperty(this.longitude, event.getGeoLong()+""));	
		if(event.getStartTime()!=null & event.getStopTime()==null)
			res.addProperty(this.zeit,model.createResource()
				.addProperty(this.zeit_start, event.getStartTime().toString()));
		if(event.getStartTime()==null & event.getStopTime()!=null)
			res.addProperty(this.zeit,model.createResource()
				.addProperty(this.zeit_ende, event.getStopTime().toString()));
		if(event.getStartTime()!=null & event.getStopTime()!=null)
			res.addProperty(this.zeit,model.createResource()
				.addProperty(this.zeit_start, event.getStartTime().toString())
				.addProperty(this.zeit_ende, event.getStopTime().toString()));
	}
	

	/**
	 * Schreibt die XML-RDF-Darstellung einer Eventanfrage auf den OutputStream
	 * 
	 * @param OutputStream
	 */
	public void writeRDF(OutputStream out) {
		String xml = "<?xml version='1.0' encoding=\"utf-8\"?>";
		byte[] er = xml.getBytes();
		try {
			out.write(er);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		model.write(out, "RDF/XML-ABBREV");
	}

	/**
	 * Traversiert das RDF Model einer Eventanfrage mittels SPARQL, erstellt
	 * daraus die KML Darstellung (zur Darstellung in Google Maps) und schreibt
	 * diese auf den Output Stream. Berücksichtigt dabei nur Events, die über
	 * eine Latitude und Longtitude Angabe verfügen.
	 * 
	 * @param Output Stream
	 */
	public void writeKML(OutputStream out) {		
		String strStartDocument = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
				"<kml xmlns=\"http://www.opengis.net/kml/2.2/\">\n" +
				"<Document>\n";
		String strEndDocument ="</Document>\n</kml>";
		String strStartCDATA = "<![CDATA[<div style=\"max-width:400px; max-height:300px; overflow:auto\">";
		String strEndCDATA = "</div>]]>";
		
		StringBuilder sb = new StringBuilder();
		sb.append(strStartDocument);

		String queryString = "PREFIX dc: <http://purl.org/dc/elements/1.1/> \n"
				+ "PREFIX wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#>\n "
				+ "PREFIX lode: <http://linkedevents.org/ontology/> \n"
				+ "PREFIX time: <http://www.w3.org/2006/time#> \n"
				+ "SELECT ?contributor ?title ?description ?publisher ?latitude ?longitude ?zeitStart ?zeitEnd "
				+ "WHERE {?contributor dc:title ?title . "
				+ "       ?contributor lode:inSpace ?node . "
				+ "       ?node wgs84:lat ?latitude . "
				+ "       ?node wgs84:long ?longitude . "
				+ "       ?contributor lode:atTime ?node2 . "
				+ "       ?node2 time:hasBeginning ?zeitStart . "
				+ "       ?node2 time:hasEnd ?zeitEnd . "
				+ "       ?contributor dc:publisher ?publisher . "
				+ "       ?contributor dc:description ?description . "
				+ "		FILTER(?latitude != \"0.0\" && ?longitude != \"0.0\") "
				+ "      }";

		com.hp.hpl.jena.query.Query query = QueryFactory.create(queryString);

		// Execute the query and obtain results
		QueryExecution qe = QueryExecutionFactory.create(query, model);
		ResultSet results = qe.execSelect();

		// Output query results
		List<QuerySolution> list = ResultSetFormatter.toList(results);
		for (QuerySolution sol : list) {	
			sb.append(Utils.xmlOpen("Placemark"));
			//Bezeichnung des Events
			sb.append(Utils.xmlOpen("name"));
			sb.append(Utils.xmlElement(Utils.kuerzen(sol.get("title").toString(),50)));
			sb.append(Utils.xmlClose("name"));
			//Description des Events
			sb.append(Utils.xmlOpen("description")+ strStartCDATA);
			//Datum
			sb.append((sol.get("zeitStart").toString() + " - " + sol.get("zeitEnd").toString() +"<br/>"));
			//Link
			sb.append("Event betrachten: <a href=\"" + sol.get("contributor").toString() + "\">" 
					+ sol.get("publisher").toString() + "</a>\n<br/>\n<br/>\n");
			//Beschreibung
			sb.append((sol.get("description").toString()));
			sb.append(strEndCDATA + Utils.xmlClose("description"));
			sb.append(Utils.xmlOpen("Point"));
			// Achtung: Es muss zuerst Longitude, dann Latitude angegeben werden
			// - auch wenn dies später in Google Earth/Maps als Lat/Long angezeigt wird
			sb.append(Utils.xmlOpen("coordinates"));
			sb.append(Utils.xmlElement(sol.get("longitude").toString() + ","+ sol.get("latitude").toString()+",0"));
			sb.append(Utils.xmlClose("coordinates"));
			sb.append(Utils.xmlClose("Point"));// Point
			sb.append(Utils.xmlClose("Placemark"));// Placemark
		}
		qe.close();
		sb.append(strEndDocument);
		try {
			out.write(sb.toString().getBytes());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Für die Darstellung als HTML-Tabelle wird dem XML-RDF-Dokument
	 * im Präfix ein entsprechendes Stylesheet zugeordnet. Der Browser
	 * kann somit automatisch eine Tabelle basierend auf der Rückgabe
	 * der jeweiligen Anfrage erstellen
	 * 
	 * @param OutputStream Stream auf den Ausgabe erfolgen soll
	 * @throws XMLStreamException
	 */
	public void writeHTML_table(OutputStream out) throws XMLStreamException {
		String temp = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
				"<?xml-stylesheet type=\"text/xsl\" href=\"table.xsl\"?>";
		try {
			out.write(temp.getBytes());
		} catch (IOException e) {
			e.printStackTrace();
		}
		model.write(out, "RDF/XML-ABBREV");

	}
	
	/**
	 * Gibt das Jena-Modell zurück
	 * @return das Modell
	 */
	public com.hp.hpl.jena.rdf.model.Model getJenaModel(){
		return this.model;
	}
}
